using System;
using System.IO;
using System.Collections;
using Microsoft.SPOT;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer;
using Gadgeteer.Interfaces;
using System.Threading;
using System.Text;
using Dairy_Town.Models;
using System.Security.Cryptography;

namespace Dairy_Town
{
    /// <summary>
    /// The Logger class is responsible for initializing and mounting the SD card, as well as performing all write and read actions.
    /// </summary>
    class Logger
    {
        private bool active = false;
        private bool ready = false;
        private Thread writerThread;

        public delegate void RecordingTickHandler(Logger logger);
        public event RecordingTickHandler RecordingTick;

        private string LogPath
        {
            get
            {
                string _logfile = @"\SD\Logs\log_" + this.NextIndex(@"SD\Logs").ToString() + ".ilf";
                return _logfile;
            }
        }
        private string donePath = @"\SD\Done\DoneFile.csv";

        private GT.Timer writeTimer;
        private GTM.GHIElectronics.SDCard drive;
        private StorageDevice sdStorage;
        private StringBuilder _writeBuffer;

        public bool isReady
        {
            get { return ready; }
        }

        public bool isActive
        {
            get { return this.writeTimer.IsRunning; }
        }

        public Logger(GTM.GHIElectronics.SDCard sdCard)
        {
            drive = sdCard;

            _writeBuffer = new StringBuilder();
            
            writeTimer = new GT.Timer(1000);
            writeTimer.Tick += writeTimer_Tick;

            new Thread(ListenForStorage).Start(); // Create seperate thread to search for an SD Card and Mount
        }

        void writeTimer_Tick(GT.Timer timer)
        {
            this.RecordingTick(this);
        }

        /// <summary>
        /// Returns next available index for log file
        /// </summary>
        /// <returns></returns>
        public int NextIndex(string directory)
        {
            string[] directories = sdStorage.ListDirectories(sdStorage.RootDirectory);
            
            bool exists = false;
            foreach (string _directory in directories)
            {
                if (_directory == directory)
                {
                    exists = true;
                    break;
                }
            }
            
            if (!exists)
                throw new System.ArgumentException("The directory '" + directory + "' does not exist.");

            return sdStorage.ListFiles(directory).Length + 1;
        }

        /// <summary>
        /// Write data to log
        /// </summary>
        /// <param name="snapshot"></param>
        public void Write(Snapshot snapshot)
        {
            snapshot.Recorded = DateTime.Now;
            string sep = ",";
            //string newline = "\r\n";

            _writeBuffer.Append(snapshot.ToString() + sep);
        }

        private void StartWriteThread()
        {
            Debug.Print("Starting write thread...");

            if (this.writerThread == null)
                this.writerThread = new Thread(Writer);

            Debug.Print("Thread state is " + this.writerThread.ThreadState.ToString());

            string state = "";
            switch (this.writerThread.ThreadState)
            {
                case ThreadState.Aborted:
                    state = "Aborted";
                    break;
                case ThreadState.Background:
                    state = "Background";
                    break;
                case ThreadState.Running:
                    state = "Running";
                    break;
                case ThreadState.Stopped:
                    state = "Stopped";
                    break;
                case ThreadState.Suspended:
                    state = "Suspended";
                    break;
                case ThreadState.Unstarted:
                    state = "Unstarted";
                    break;
                case ThreadState.AbortRequested:
                    state = "Abort Requested";
                    break;
                case ThreadState.StopRequested:
                    state = "Stop Requested";
                    break;
                case ThreadState.SuspendRequested:
                    state = "Suspend Requested";
                    break;
                case ThreadState.WaitSleepJoin:
                    state = "Wait Sleep Join";
                    break;
            }

            Debug.Print("Thread State is " + state);

            if (this.writerThread.ThreadState == ThreadState.Stopped || this.writerThread.ThreadState == ThreadState.Unstarted)
                this.writerThread.Start();
            else if (this.writerThread.ThreadState == ThreadState.Suspended)
                this.writerThread.Resume();
        }

        public void StartCollection()
        {
            Debug.Print("Starting data collection...");
            this.StartWriteThread();

            if (!this.writeTimer.IsRunning)
                this.writeTimer.Start();
        }

        public void StopCollection()
        {
            if(this.writerThread != null)
                this.writerThread.Abort();

            if(this.writeTimer != null)
                this.writeTimer.Stop();
        }

        /// <summary>
        /// Looks for active storage 
        /// </summary>
        private void ListenForStorage()
        {
            while (!drive.IsCardMounted)
            {
                if (drive.IsCardInserted)
                {
                    Thread.Sleep(500);
                    try
                    {
                        if (!drive.IsCardMounted)
                            this.drive.MountSDCard();
                    }
                    catch (System.InvalidOperationException ex)
                    {
                        Debug.Print("Could not mount SD card. " + ex.Message);
                    }
                }
                else
                {
                    Debug.Print("No SD Card found");
                }
                Thread.Sleep(3000);
            }

            this.ConfigureSD();
        }

        /// <summary>
        /// Called after the SD card is mounted and proceeds to check for proper setup
        /// If card has not been setup for this device, method proceeds to create a "Debug" folder and "Logs" folder
        /// Following the creation of these folders
        /// </summary>
        private void ConfigureSD()
        {
            Debug.Print("Configuring SD card...");

            bool configured = false;
            bool doneFile = false;
            bool transferDir = false;
            sdStorage = drive.GetStorageDevice();

            string[] directories = sdStorage.ListDirectories(sdStorage.RootDirectory);

            foreach (string directory in directories)
            {
                if (directory.Equals("Logs"))
                {
                    configured = true;
                }
                if (directory.Equals("Done"))
                {
                    doneFile = true;
                }
                if (directory.Equals("Queue"))
                {
                    transferDir = true;
                }
            }

            if (!configured)
            {
                sdStorage.CreateDirectory(sdStorage.RootDirectory + Path.DirectorySeparatorChar + "Logs");
            }
            if (!transferDir)
            {
                sdStorage.CreateDirectory(sdStorage.RootDirectory + Path.DirectorySeparatorChar + "Queue");
            }
            if (!doneFile)
            {
                sdStorage.CreateDirectory(sdStorage.RootDirectory + Path.DirectorySeparatorChar + "Done");
                sdStorage.WriteFile(donePath, System.Text.UTF8Encoding.UTF8.GetBytes("Done"));
            }

            ready = true;

            if (this.isActive)
                this.StartWriteThread();
        }

        private void Writer()
        {
            Debug.Print("Writer thread is running");
            /*
             * Designed to only allow one write operation to occur
             */
            int secs = 0;
            while (true)
            {
                Debug.Print(10 - secs + "...");

                Thread.Sleep(1000);
                secs++;

                if (secs >= 10 && isReady) // Write buffer to SD Card after 10 seconds and clear buffer
                {
                    secs = 0;
                    string buffer = _writeBuffer.ToString();
                    _writeBuffer.Clear();
                    WriteToLog(buffer);
                }
            }
        }

        private void WriteToLog(string Data)
        {
            if (!isReady)
                return;

            Debug.Print("Writing " + Data);

            if (!File.Exists(LogPath))
            {
                WriteLogLine(LogPath, Data);
            }
            
            Debug.Print("Write complete");
        }

        private void WriteLogLine(string path, string contents)
        {
            if (!File.Exists(path))
            {
                sdStorage.WriteFile(path, System.Text.UTF8Encoding.UTF8.GetBytes(contents));
            }
        }

        /// <summary>
        /// Called when SD card is mounted
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="SDCard"></param>               
        private void sdCard_SDCardMounted(GTM.GHIElectronics.SDCard sender, StorageDevice SDCard)
        {
            Debug.Print("SD Card mounted");

            this.ConfigureSD();
        }

        /// <summary>
        /// Called when SD card is unmounted
        /// </summary>
        /// <param name="sender"></param>
        void drive_SDCardUnmounted(GTM.GHIElectronics.SDCard sender)
        {
            sdStorage = null;
            //Console.Display("SD Card unmounted");

            ready = false;
        }

        public static string ReadLineEx(StreamReader sr)
        {
            int newChar = 0;
            int bufLen = 512; // NOTE: the smaller buffer size.
            char[] readLineBuff = new char[bufLen];
            int growSize = 512;
            int curPos = 0;
            while ((newChar = sr.Read()) != -1)
            {
                if (curPos == bufLen)
                {
                    if ((bufLen + growSize) > 0xffff)
                    {
                        throw new Exception();
                    }
                    char[] tempBuf = new char[bufLen + growSize];
                    Array.Copy(readLineBuff, 0, tempBuf, 0, bufLen);
                    readLineBuff = tempBuf;
                    bufLen += growSize;
                }
                readLineBuff[curPos] = (char)newChar;
                if (readLineBuff[curPos] == '\n')
                {
                    return new string(readLineBuff, 0, curPos);
                }
                if (readLineBuff[curPos] == '\r')
                {
                    sr.Read();
                    return new string(readLineBuff, 0, curPos);
                }
                curPos++;
            }

            if (curPos == 0) return null; // Null fix.
            return new string(readLineBuff, 0, curPos);
        }

        /// <summary>
        /// Deletes each of the logs on the SD card upon the successful transfer of files to the gateway server.
        /// </summary>
        public void reset()
        {
            StorageDevice sdCard = drive.GetStorageDevice();
            string[] files = sdCard.ListFiles(sdCard.RootDirectory + Path.DirectorySeparatorChar + "Logs");
            foreach (string path in files)
            {
                sdCard.Delete(path);
            }
        }

        /// <summary>
        /// Generates unique hash for specified file
        /// </summary>
        /// <param name="filePath">File to open</param>
        /// <returns>Unique hash</returns>
        public string Checksum(string filePath, bool isPath = true)
        {
            return this.Checksum(this.Read(filePath));
        }

        /// <summary>
        /// Generates unique hash for content
        /// </summary>
        /// <param name="contents">string to be hased</param>
        /// <returns>Unique hash</returns>
        public string Checksum(string contents)
        {
            string md5 = null;
            using (HashAlgorithm cryp = new HashAlgorithm(HashAlgorithmType.MD5))
            {
                byte[] hash = cryp.ComputeHash(Encoding.UTF8.GetBytes(contents));
                md5 = Convert.ToBase64String(hash);
            }

            return md5;
        }

        /// <summary>
        /// Reads a file from the SD card into a string.
        /// </summary>
        /// <param name="path">Pathname of the desired file.</param>
        /// <returns>String containing the contents of the requested file.</returns>
        public string Read(string path)
        {
            StorageDevice sdCard = drive.GetStorageDevice();

            string[] components = path.Split('\\');

            string filename = components[components.Length - 1];
            string directory = path.Substring(0, (path.Length - filename.Length));

            if (!fileExists(filename, directory))
            {
                //Console.Display("'" + filename + "' does not exist");
                return "";
            }

            try
            {
                string buffer = "";
                using (var oStream = sdCard.Open(path, FileMode.Open, FileAccess.ReadWrite))
                {
                    using (StreamReader reader = new StreamReader(oStream))
                    {
                        buffer = reader.ReadToEnd();
                    }
                }

                return buffer;
            }
            catch (SystemException)
            {
                return "";
            }
        }

        /// <summary>
        /// Checks storage device for existing file and returns true if specified file does exists
        /// </summary>
        /// <param name="filename">The name of the file to be checked</param>
        /// <param name="directory">The directory which the file resides</param>
        /// <returns></returns>        
        private bool fileExists(string filename, string directory)
        {
            StorageDevice sdCard = drive.GetStorageDevice();

            string[] files = sdCard.ListFiles(directory);

            foreach (string file in files)
            {
                string[] components = file.Split('\\');
                string comparator = components[components.Length - 1];
                if (filename.Equals(comparator))
                {
                    return true;
                }
            }

            return false;
        }
    }
}
